/**
 * FSU Steggo in Encoding in Image Files
 */

package edu.fsu.cs.buzbee.steggo;


import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.apache.commons.cli.*;

public class Main {
	
	public static void main(String[] args)
	{
		
		
	
		
		Options options = new Options();
		
		options.addOption("encode", false, "Encoding mode. Requires -image, -input and -output");
		options.addOption("decode", false, "Decoding mode. Requires -image and -output");
		//options.addOption("analyze", false, "Analysis mode. Requires -image");
		options.addOption("lsb", false, "Least significant bit coding mode. Requires -encode or -decode.");
		options.addOption("matrix", false, "Matrix-over-LSB coding mode. Requires -encode or -decode. [Default]");
		options.addOption("image", true, "Image to encode data into or decode from");
		options.addOption("input", true, "Data file to encode. Must be used with -encode");
		options.addOption("output", true, "File to output result of encoding/decoding to.");
		options.addOption("k", true, "k value produced during matrix encoding. Required for matrix decoding.");
		options.addOption("size", true, "Size of encoded file in bytes. Required for matrix decoding.");
		
		
		CommandLineParser parser = new GnuParser();
		try {
			CommandLine line = parser.parse(options, args);
			
			if (line.hasOption("encode"))
			{
				if (!line.hasOption("image"))
				{
					System.err.println("Must specify -image <image file>");
				}
				else if (!line.hasOption("input"))
				{
					System.err.println("Must specify -input <data file>");
				}
				else if (!line.hasOption("output"))
				{
					System.err.println("Must specify -output <destination image>");
				}
				else // image and input and output present with encode
				{
					File destination = new File(line.getOptionValue("output"));
					
					File input = new File(line.getOptionValue("input"));
					
					File image = new File(line.getOptionValue("image"));
				
					if (line.hasOption("lsb"))
					{					
						try {
							System.out.println ("Least significant bit encoding mode.");
							BufferedImage bi = ImageIO.read(image);
							Encoder encoder = new Encoder(bi);
							try {
								encoder.encode(input, destination);
							} catch (IOException e)
							{
								System.err.println("Error: "+e.getMessage());
							}
						} catch (IOException e) {
							System.err.println("Could not read input image: "+e.getMessage());
						}
					}
					else
					{
						System.out.println ("Matrix over least significant bit encoding mode.");
						try {
							
							BufferedImage bi = ImageIO.read(image);
							MatrixEncoder encoder = new MatrixEncoder(bi);
							try {
								encoder.encode(input, destination);
							} catch (IOException e)
							{
								System.err.println("Error: "+e.getMessage());
							}
						} catch (IOException e) {
							System.err.println("Could not read input image: "+e.getMessage());
						}
					}
					
				}
			}
			else if (line.hasOption("decode"))
			{
				if (!line.hasOption("image"))
				{
					System.err.println("Must specify -image <image file>");
				}
				else if (!line.hasOption("output"))
				{
					System.err.println("Must specify -output <destination data>");
				}
				else
				{
					if (line.hasOption("lsb"))
					{
						System.out.println("Least-significant bit decoding mode.");
						File destination = new File(line.getOptionValue("output"));
						
						File image = new File(line.getOptionValue("image"));
						
						try {
							BufferedImage bi = ImageIO.read(image);
							Decoder decoder = new Decoder(bi);
							try {
								decoder.decode(destination);
							} catch (IOException e)
							{
								System.err.println("Error: "+e.getMessage());
							} catch (InvalidFormatException e) {
								// TODO Auto-generated catch block
								System.err.println("Image Error: "+e.getMessage());
							}
						} catch (IOException e) {
							System.err.println("Could not read input image: "+e.getMessage());
						}
					}
					else
					{
						if (!line.hasOption("k"))
						{
							System.err.println("This option requires -k <N>");
							return;
						}
						if (!line.hasOption("size"))
						{
							System.err.println("This option requires -size <N> - the size of the encoded file in bytes.");
							return;
						}
						int k = 0;
						long size = 0;
						try {
							k = Integer.parseInt(line.getOptionValue("k"));
						} catch (Throwable t) {
							System.out.println("Invalid value for -k. k was given during encoding.");
							return;
						}
						
						try {
							size = Long.parseLong(line.getOptionValue("size"));
						} catch (Throwable t)
						{
							System.err.println("Invalid value for -size.  The size was given during encoding.");
							return;
						}
						
						System.out.println("Matrix over least significant bit decoding mode.");
						;
						File destination = new File(line.getOptionValue("output"));
						
						File image = new File(line.getOptionValue("image"));
						
						try {
							BufferedImage bi = ImageIO.read(image);
							MatrixDecoder decoder = new MatrixDecoder(bi);
							try {
								decoder.decode(destination, k, size);
							} catch (IOException e)
							{
								System.err.println("Error: "+e.getMessage());
							} catch (InvalidFormatException e) {
								// TODO Auto-generated catch block
								System.err.println("Image Error: "+e.getMessage());
							}
						} catch (IOException e) {
							System.err.println("Could not read input image: "+e.getMessage());
						}
						
					}
				}
			}
			// Analyze not completed (only effective for LSB mode)
			else if (line.hasOption("analyze"))
			{
				if (!line.hasOption("image"))
				{
					System.err.println("Must specify -image <image file>");
				}
				else
				{
					
					File image = new File(line.getOptionValue("image"));
					try {
						BufferedImage bi = ImageIO.read(image);
						Decoder d = new Decoder(bi);
						Encoder e = new Encoder(bi);
						boolean encoded = false;
						for (int i = 1; i <= 8; ++i)
						{
							d.setEncodeBits(i);
							if (d.decodeInt() == Encoder.MAGIC)
							{
								encoded = true;
								break;
							}
							d.reset();
						}
						System.out.println("\n");
						if (encoded)
							System.out.println("Image contains hidden message encoded in the lower "+d.getEncodeBits() + " bits of each RGB component.");
						else
							System.out.println("Image does not appear to contain a hidden message.");
						
						System.out.println("\n");
						System.out.println("Due to the encoding algorithm implemented, two encoded bytes cannot share the same pixel.  As such, maximum encodable size is limited by the number of available pixels in the file.  Since there are 3 components per pixels, 3 LSBs yield 9 bits per pixel, and is the max.");
						System.out.println("\nThe following table lists the maximum encodable size with respect to the number of least significant bits (1-3) used for the encoding.\n");
						System.out.println("[Number of LSBs per Component]: Max encodable bytes");
						for (int i = 1; i <= 3; ++i)
						{
							
							int max = e.getMaxSize(i);
							max = Math.min(max, (bi.getWidth() * bi.getHeight()) - Encoder.PREAMBLE_SIZE);
							System.out.printf("[%d]: %d\n",i,  max);
						}
						
					} catch (IOException e) {
						System.err.println("Could not read input image: "+e.getMessage());
					}
				}
			}
			else
			{
				new HelpFormatter().printHelp("steggo", options);
			}
			
		} catch (ParseException e) {
			System.err.println(e.getMessage());
		}
	}
	
}
